javacしか知らなかった原始人がGradleというビルドツールで現代人を目指した話
こんにちは、平野です。
この年末年始、ずっと理解したいと思ってたものに色々と触る時間が取れたので、 非常に基本的な事柄ですが、自分なりに吸収したことをアウトプットして行きたいと思います!
ということで、Gradleを使ってJavaのプログラムをビルドして、 ビルドツールとかぜんぜん使ったことない原始人から現代人への進化を目指します! なお、IDEは使わないでコマンドラインからの確認を行います。 (だから現代人になれないのでは?)
スタート地点
この記事の内容を行う前の私の状況です。
- Javaのプログラムの基本的な部分はまあまあ理解している
- Javaのビルドと言えばCLASSPATH設定してコマンドラインで
javac
するの一択- そのあと
jar
コマンドで固める
- そのあと
- Eclipseはちょっと使ったことあるけど、CLASSPATHの設定の仕方も思い出せない
- Gradleは触ったことあるけど、人が書いたものを実行しただけ
また他のビルドツールについてもAnt?Maven?なにそれ、新しいポケモン?な感じです。
Gradleのインストール&検証環境
Macならインストールは以下のコマンドだけのはずです。他は特に行っていません (と思います。実際インストールしたのは結構前なので・・・)。
brew install gradle
検証に使用したバージョンは以下です。
$ gradle -v ------------------------------------------------------------ Gradle 6.0.1 ------------------------------------------------------------ Build time: 2019-11-18 20:25:01 UTC Revision: fad121066a68c4701acd362daf4287a7c309a0f5 Kotlin: 1.3.50 Groovy: 2.5.8 Ant: Apache Ant(TM) version 1.10.7 compiled on September 1 2019 JVM: 1.8.0_181 (Oracle Corporation 25.181-b13) OS: Mac OS X 10.14.6 x86_64
一番最初のビルド
早速最小限に近いようなプログラムのビルドを行ってみます。 以下のようなJava初学者が最初に書くプログラムを用意します。
public class Test { public static void main(String[] args) { System.out.println("HelloWorld"); } }
(私が知っている唯一の)ビルド&実行の方法は以下のような感じですね。
$ javac Test.java && java Test HelloWorld
まずはこれをGradleを使って行うことが最初のゴールです。
このファイルを以下のパスに設置します。
(以降、今回の作業の起点となるディレクトリをsample02
とした内容になっています)
src/main/java/Test.java
この場所はGradleの決まりごとで、無視しても良いことは特になさそうなので、 初めからこの場所に置いておくことにします。
次にbuild.gradle
という名前で、Gradleで行うビルド内容の定義を書いていきます。
まずはこれだけ書きます。
apply plugin: 'java'
最初、「基本的なプログラムのビルドぐらい、設定ファイルになにも書かないでもできないもんかな?」と思ったのですが、
Gradleのコア部分には、実際の自動化に役に立つような機能は含まれていません。 これは意図的なもので、Javaのコードをコンパイルしたりといった便利な機能は、全てプラグインにより追加されます。
Gradle User Guide - 第21章 Gradleのプラグインについて
のように書かれており、Gradleの思想的に、 あくまでもJavaの基本的なコンパイルもプラグインで実行するというスタンスのようです。
また、このファイルの書式はgroovyというプログラミング言語とのことですが、 少なくとも最初はそんなことは意識する必要はなく、 ただの設定ファイルだと思っておいて問題ないかと思います。
閑話休題、ここまでを準備した状態でgradle tasks
と実行することで、
実行できるサブコマンドの一覧を得ることができます。
$ gradle tasks > Task :tasks ------------------------------------------------------------ Tasks runnable from root project ------------------------------------------------------------ Build tasks ----------- assemble - Assembles the outputs of this project. build - Assembles and tests this project. buildDependents - Assembles and tests this project and all projects that depend on it. buildNeeded - Assembles and tests this project and all projects it depends on. classes - Assembles main classes. clean - Deletes the build directory. jar - Assembles a jar archive containing the main classes. testClasses - Assembles test classes. (以下略)
assemble
やbuild
など、似たようなものが並んでいます。
今回はテストとか全然関係ないのでgradle assemble
を行ってみます。
$ gradle assemble BUILD SUCCESSFUL in 546ms 2 actionable tasks: 2 executed
成功しました。
build
ディレクトリが作成され、中にclassとJarファイルが作成されました。
build/classes/java/main/Test.class build/libs/sample02.jar
期待したものが出力されたので、それぞれ確認してみます。
$ java -cp build/classes/java/main/ Test HelloWorld $ jar tvf build/libs/sample02.jar 0 Mon Dec 16 12:17:30 JST 2019 META-INF/ 25 Mon Dec 16 12:17:30 JST 2019 META-INF/MANIFEST.MF 514 Mon Dec 16 12:17:30 JST 2019 Test.class $ java -cp build/libs/sample02.jar Test HelloWorld
classファイル、Jarファイル共に期待通りの動作になっています。
ということで、もっとも単純なjavac Test.java
と同じことができました!
外部ライブラリ
さて、これだけだとGradleを使う旨味がないので、外部ライブラリを使用してみます。 今回はちょっといきなり飛躍しますが、AWS SDKを使ってみようと思います。
Javaのプログラムとして以下のTest.java
を準備します。
import com.amazonaws.services.s3.AmazonS3; import com.amazonaws.services.s3.AmazonS3Client; import com.amazonaws.services.s3.model.Bucket; public class Test { public static void main(String args[]) { AmazonS3 s3Client = new AmazonS3Client(); for (Bucket bucket: s3Client.listBuckets()) { System.out.println(bucket.getName()); } } }
S3のクライアントを準備して、バケットの名前一覧を表示するプログラムです。 あとはimportしているクラスが見つかれば正しく実行ができるはずです。
とりあえずそのままビルドして失敗してみます。
$ gradle assemble > Task :compileJava FAILED /Users/hirano.shigetoshi/study/20191213-java-gradle/sample02/src/main/java/Test.java:1: エラー: パッケージcom.amazonaws.services.s3は存在しません import com.amazonaws.services.s3.AmazonS3; ^ (以下略)
クラスが見つからない、という期待通りのエラーが返ってきました。 こういうのを一つ一つ確認するのは大事です。 ということで、AWS SDKのクラスと紐付けてあげなくてはいけません。
もっとも基本的なやり方としてはJarファイルをダウンロードしてきて、 それにCLASSPATHを通して実行する形になります。
しかし、イマドキはそんなことをする必要はないようです!
Gradleではbuild.gradle
に
どのライブラリが必要か、
どのリポジトリから取得するか、
を書く事で、必要なものを自動的に取得してビルドを行うことができます!
すごいですねー(普通?)
ということで、build.gradle
に以下のように記述します。
apply plugin: 'java' repositories { mavenCentral() } dependencies { implementation 'com.amazonaws:aws-java-sdk:1.11.692' }
repositories
にライブラリを見つけに行くリポジトリ、
dependencies
に必要なライブラリを記述します。
mavenCentral()
については、他に例えばどんなものが指定できるのかまだ特に調べていませんが、
少なくともここで見つからないものが出てくるまではこのまま特に変更しなくても良さそうです。
dependencies
に書いた内容はこちらに記載されているものをそのまま書いています。
dependencies
に書くものの探し方としては、検索窓にimportするクラスを入れ、
見つからなければ末端(右側)を少しずつ削っていき、
一番それっぽいものを指定すれば大丈夫そうです。
com.amazonaws.services.s3.AmazonS3 com.amazonaws.services.s3 com.amazonaws.services com.amazonaws # ここで見つかった
早速これでビルドして行きます。
一応前回の結果のクリアのため、gradle clean
も行います。
gradle clean assemble
とするとclean
してからassemble
を行います。
$ gradle clean assemble > Task :compileJava 注意:/Users/hirano.shigetoshi/study/20191213-java-gradle/sample02/src/main/java/Test.javaは非推奨のAPIを使用またはオーバーライドしています。 注意:詳細は、-Xlint:deprecationオプションを指定して再コンパイルしてください。 BUILD SUCCESSFUL in 687ms 3 actionable tasks: 3 executed
問題なくビルド完了です。 古いクラスを使っている警告が出ていますが、今回は目をつぶってください。
さて、ビルドできたので実行!と行きたいのですが、 残念ながら必要なライブラリが手元にないので、それらがないとやはり実行はできません。
Fat Jarの作成
上記の解決策の一つとして、必要なクラス全部入りのJarを作ってみます。
このようなJarファイルはFat Jarと呼ばれるようです。
そのためにGradle Shadowというプラグインを使います。
使い方として、build.gradle
を以下のように書き換えます。
plugins { id 'java' id 'com.github.johnrengelman.shadow' version '2.0.3' } repositories { mavenCentral() } dependencies { implementation 'com.amazonaws:aws-java-sdk:1.11.692' } shadowJar { zip64 true }
plugins
は、従来からあったjava
と合わせて、上記のように書き換えます。
これでJavaとGradle Shadowと2つのプラグインを使うという意味になります。
shadowJar
のzip64 true
の部分は(大事そうに見えるけど)本質ではないです。
そのままビルドすると容量が大きくて以下のように言われるので、つけました。
To build this archive, please enable the zip64 extension.
これで準備完了。ビルドを行います。
従来のgradle assemble
等のコマンドではFat Jarは作成できないのでgradle shadowJar
と実行します。
このコマンドの存在はgradle tasks
すると確認することができます。
Shadow tasks ------------ knows - Do you know who knows? shadowJar - Create a combined JAR of project and runtime dependencies
では、ビルドします。
$ gradle clean shadowJar > Task :compileJava 注意:/Users/hirano.shigetoshi/study/20191213-java-gradle/sample02/src/main/java/Test.javaは非推奨のAPIを使用またはオーバーライドしています。 注意:詳細は、-Xlint:deprecationオプションを指定して再コンパイルしてください。 Deprecated Gradle features were used in this build, making it incompatible with Gradle 7.0. Use '--warning-mode all' to show the individual deprecation warnings. See https://docs.gradle.org/6.0.1/userguide/command_line_interface.html#sec:command_line_warnings BUILD SUCCESSFUL in 24s 3 actionable tasks: 3 executed
24秒ほどと、ちょっと時間がかかってビルド完了です。
作成されるJarファイルは変わらずbuild/libs/sample02.jar
ですが、
容量を見てみると153MBありました。
ちなみにFat Jarでないときは1kB未満だったので、
かなりたくさんのclassが含まれていることがわかります。
さて、実行です。
$ java -cp build/libs/sample02.jar Test shigetoshi-amazon-s3 # S3にあるバケット一覧が表示された
単独のjarファイルをCLASSPATHに通しただけですが、 AWS SDKを使用してAWSのリソースにアクセスできました!!
使ってみた感想
最初Gradleを使う前に、自力でJarファイルをダウンロードしてCLASSPATHを通して実行したのですが、 これだと、AWS SDKのクラスが使う別のJarも探してくる必要があり、大変でした。
まぁそれでも5個くらいのJarファイルを用意したことでClassNotFoundは解消されたのですが、 どうやらバージョンが適合していないようで、よくわからない所でエラーが出てしまっていました。 もちろんバージョンも正しいものが準備できたら問題なく動作するのでしょうが、 どのjarが問題なのかを切り分けるのも大変だし、、、と考えていて、
「そもそも(ある程度世の中で知られている)必要なクラスを自力で探してくるのって多分機械的にできるし、 わざわざ人手でやらない方がよくない?」 ということに気づいたのでした(遅っっっっっっっっっっっっっっ!
ビルドツールはこの辺をやってくれるので本当に便利です。 欲しいクラスは数個なのに芋づる式に複数のクラスをたくさん探して来なくちゃいけないのは本当に心が折れますし、 こんなことに時間使いたくないなぁという思いが解消されるのは素晴らしいです!
まとめ
JavaのビルドツールとしてGradleを手探りで触ってみました。
天下り的な所もあるものの、最小限に設定を追加していくことで Gradleの使い方がだいぶわかってきました。
これで少し現代人に近づけた気がします。 やっぱり機械的にできるものはどんどん自動化して行きたいですね!